/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.bpm.engine.impl.persistence.entity.data;

import com.je.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;
import com.je.bpm.engine.impl.db.DbSqlSession;
import com.je.bpm.engine.impl.persistence.AbstractManager;
import com.je.bpm.engine.impl.persistence.CachedEntityMatcher;
import com.je.bpm.engine.impl.persistence.SingleCachedEntityMatcher;
import com.je.bpm.engine.impl.persistence.cache.CachedEntity;
import com.je.bpm.engine.impl.persistence.cache.EntityCache;
import com.je.bpm.engine.impl.persistence.entity.Entity;
import java.util.*;


public abstract class AbstractDataManager<EntityImpl extends Entity> extends AbstractManager implements DataManager<EntityImpl> {

    public AbstractDataManager(ProcessEngineConfigurationImpl processEngineConfiguration) {
        super(processEngineConfiguration);
    }

    public abstract Class<? extends EntityImpl> getManagedEntityClass();

    public List<Class<? extends EntityImpl>> getManagedEntitySubClasses() {
        return null;
    }

    protected DbSqlSession getDbSqlSession() {
        return getSession(DbSqlSession.class);
    }

    protected EntityCache getEntityCache() {
        return getSession(EntityCache.class);
    }

    @Override
    public EntityImpl findById(String entityId) {
        if (entityId == null) {
            return null;
        }

        // Cache
        EntityImpl cachedEntity = getEntityCache().findInCache(getManagedEntityClass(), entityId);
        if (cachedEntity != null) {
            return cachedEntity;
        }

        // Database
        return getDbSqlSession().selectById(getManagedEntityClass(), entityId, false);
    }

    @Override
    public void insert(EntityImpl entity) {
        getDbSqlSession().insert(entity);
    }

    @Override
    public EntityImpl update(EntityImpl entity) {
        getDbSqlSession().update(entity);
        return entity;
    }

    @Override
    public void delete(String id) {
        EntityImpl entity = findById(id);
        delete(entity);
    }

    @Override
    public void delete(EntityImpl entity) {
        getDbSqlSession().delete(entity);
    }

    @SuppressWarnings("unchecked")
    protected EntityImpl getEntity(String selectQuery, Object parameter, SingleCachedEntityMatcher<EntityImpl> cachedEntityMatcher, boolean checkDatabase) {
        // Cache
        for (EntityImpl cachedEntity : getEntityCache().findInCache(getManagedEntityClass())) {
            if (cachedEntityMatcher.isRetained(cachedEntity, parameter)) {
                return cachedEntity;
            }
        }

        // Database
        if (checkDatabase) {
            return (EntityImpl) getDbSqlSession().selectOne(selectQuery, parameter);
        }

        return null;
    }

    /**
     * Gets a list by querying the database and the cache using {@link CachedEntityMatcher}.
     * First, the entities are fetched from the database using the provided query.
     * The cache is then queried for the entities of the same type. If an entity matches
     * the {@link CachedEntityMatcher} condition, it replaces the entity from the database (as it is newer).
     *
     * @param dbQueryName         The query name that needs to be executed.
     * @param parameter           The parameters for the query.
     * @param cachedEntityMatcher The matcher used to determine which entities from the cache needs to be retained
     * @param checkCache          If false, no cache check will be done, and the returned list will simply be the list from the database.
     */
    @SuppressWarnings("unchecked")
    protected List<EntityImpl> getList(String dbQueryName, Object parameter, CachedEntityMatcher<EntityImpl> cachedEntityMatcher, boolean checkCache) {
        Collection<EntityImpl> result = getDbSqlSession().selectList(dbQueryName, parameter);
        if (checkCache) {
            Collection<CachedEntity> cachedObjects = getEntityCache().findInCacheAsCachedObjects(getManagedEntityClass());
            if ((cachedObjects != null && cachedObjects.size() > 0) || getManagedEntitySubClasses() != null) {
                HashMap<String, EntityImpl> entityMap = new HashMap<String, EntityImpl>(result.size());
                // Database entities
                for (EntityImpl entity : result) {
                    entityMap.put(entity.getId(), entity);
                }

                // Cache entities
                if (cachedObjects != null && cachedEntityMatcher != null) {
                    for (CachedEntity cachedObject : cachedObjects) {
                        EntityImpl cachedEntity = (EntityImpl) cachedObject.getEntity();
                        if (cachedEntityMatcher.isRetained(result, cachedObjects, cachedEntity, parameter)) {
                            entityMap.put(cachedEntity.getId(), cachedEntity); // will overwite db version with newer version
                        }
                    }
                }

                if (getManagedEntitySubClasses() != null && cachedEntityMatcher != null) {
                    for (Class<? extends EntityImpl> entitySubClass : getManagedEntitySubClasses()) {
                        Collection<CachedEntity> subclassCachedObjects = getEntityCache().findInCacheAsCachedObjects(entitySubClass);
                        if (subclassCachedObjects != null) {
                            for (CachedEntity subclassCachedObject : subclassCachedObjects) {
                                EntityImpl cachedSubclassEntity = (EntityImpl) subclassCachedObject.getEntity();
                                if (cachedEntityMatcher.isRetained(result, cachedObjects, cachedSubclassEntity, parameter)) {
                                    entityMap.put(cachedSubclassEntity.getId(), cachedSubclassEntity); // will overwite db version with newer version
                                }
                            }
                        }
                    }
                }

                result = entityMap.values();

            }

        }

        // Remove entries which are already deleted
        if (result.size() > 0) {
            Iterator<EntityImpl> resultIterator = result.iterator();
            while (resultIterator.hasNext()) {
                if (getDbSqlSession().isEntityToBeDeleted(resultIterator.next())) {
                    resultIterator.remove();
                }
            }
        }

        return new ArrayList<EntityImpl>(result);
    }

    protected List<EntityImpl> getListFromCache(CachedEntityMatcher<EntityImpl> entityMatcher, Object parameter) {
        Collection<CachedEntity> cachedObjects = getEntityCache().findInCacheAsCachedObjects(getManagedEntityClass());
        DbSqlSession dbSqlSession = getDbSqlSession();
        List<EntityImpl> result = new ArrayList<EntityImpl>(cachedObjects.size());
        if (cachedObjects != null && entityMatcher != null) {
            for (CachedEntity cachedObject : cachedObjects) {
                EntityImpl cachedEntity = (EntityImpl) cachedObject.getEntity();
                if (entityMatcher.isRetained(null, cachedObjects, cachedEntity, parameter) && !dbSqlSession.isEntityToBeDeleted(cachedEntity)) {
                    result.add(cachedEntity);
                }
            }
        }

        if (getManagedEntitySubClasses() != null && entityMatcher != null) {
            for (Class<? extends EntityImpl> entitySubClass : getManagedEntitySubClasses()) {
                Collection<CachedEntity> subclassCachedObjects = getEntityCache().findInCacheAsCachedObjects(entitySubClass);
                if (subclassCachedObjects != null) {
                    for (CachedEntity subclassCachedObject : subclassCachedObjects) {
                        EntityImpl cachedSubclassEntity = (EntityImpl) subclassCachedObject.getEntity();
                        if (entityMatcher.isRetained(null, cachedObjects, cachedSubclassEntity, parameter) && !dbSqlSession.isEntityToBeDeleted(cachedSubclassEntity)) {
                            result.add(cachedSubclassEntity);
                        }
                    }
                }
            }
        }

        return result;
    }


}
